#include "HTTPServer.h"
#include <exception>
#include <functional>

HTTPServer::HTTPServer(bool UseRedis, bool UseMultiThread) {
    port = DEFAULT_PORT;
    backlog = DEFAULT_BACKLOG;
    this->requestBuf = new char[N]; //4kB
    this->serverAddress = new struct sockaddr_in;
    this->clientAddress = new struct sockaddr_in;
    this->httpParserSettings = new llhttp_settings_t;
    this->codec = new urlEncoderAndDecoder();
    this->pool = nullptr;
    this->redis = nullptr;
    this->requestProcessPtr = nullptr;
    this->UseRedis = UseRedis;
    this->UseMultiThread = UseMultiThread;

    if (UseRedis) {
        redis = new Redis();
        try {
            redis->connect();
            logger->info("[HTTPServer] redis连接成功.");
            spdlog::info("[HTTPServer] redis连接成功.");
        } catch (std::exception& e) {
            this->UseRedis = false;
            delete redis;
            redis = nullptr;
        }
    }

    if (UseMultiThread) {
        // 首先获取CPU数量N 线程池大小 N + 1 或者 2N
        auto num = std::thread::hardware_concurrency();
        if (num == 0) {
            num = 1;
        }

        this->pool = new threadPool(num);
        logger->info("[HTTPServer] 启用多线程:线程数{}", num);
        spdlog::info("[HTTPServer] 启用多线程:线程数{}", num);
    }

    llhttp_settings_init(this->httpParserSettings);
    this->setCallbacks(this->httpParserSettings); // 设置回调函数 llhttp通过回调完成http数据的解析
}

// 析构函数部分
HTTPServer::~HTTPServer(){
    delete[] this->requestBuf;
    delete this->serverAddress;
    delete this->clientAddress;
    delete this->redis;
    delete this->pool;
    logger->info("[HTTPServer] 析构");
}

int HTTPServer::getServerSocketAndListen() {
    int serverFd(-1);
    this->serverAddress->sin_family = AF_INET;  // 定义协议族.
    this->serverAddress->sin_port = htons(this->port); // 绑定端口 
    this->serverAddress->sin_addr.s_addr = INADDR_ANY;// 获取本机地址

    //建立socket, 获取一个文件描述符
    if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        logger->error("[HTTPServer] socket建立失败:{}({})", __FILE__, __LINE__);
        spdlog::error("[HTTPServer] socket建立失败:{}({})", __FILE__, __LINE__);
        exit(EXIT_FAILURE);
    }

    // 绑定端口
    if ((bind(serverFd, (struct sockaddr*) this->serverAddress, sizeof(struct sockaddr))) < 0) {
        logger->error("[HTTPServer] socket建立失败,端口{}已占用", this->port);
        spdlog::error("[HTTPServer] socket建立失败,端口{}已占用", this->port);
        exit(EXIT_FAILURE);
    }
    logger->info("socket建立成功.");
    spdlog::info("socket建立成功.");

    //监听
    if (listen(serverFd, backlog) < 0) {
        logger->error("[HTTPServer] 启动监听失败.");
        spdlog::error("[HTTPServer] 启动监听失败.");
        exit(EXIT_FAILURE);
    }

    logger->info("[HTTPServer] 监听端口:{}", port);
    spdlog::info("[HTTPServer] 监听端口:{}", port);

    return serverFd;
}

void HTTPServer::setCallbacks(llhttp_settings_t* settings) {
    // 静态成员函数 对成员不做修改 
    settings->on_message_begin = HTTPServer::callback_on_message_begin;
    settings->on_status = nullptr;
    settings->on_chunk_header = nullptr;
    settings->on_chunk_complete = nullptr;
    settings->on_url = HTTPServer::callback_on_url;
    settings->on_header_field = HTTPServer::callback_on_header_field;
    settings->on_header_value = HTTPServer::callback_on_header_value;
    settings->on_headers_complete = HTTPServer::callback_on_headers_complete;
    settings->on_body = HTTPServer::callback_on_body;
    settings->on_message_complete = HTTPServer::callback_on_message_complete;
}


// 设定新的端口
int HTTPServer::setNewPort(int newPort) {
    if (this->port == newPort) {
        logger->info("[HTTPSERVER] 警告!设定的新端口和原端口一致!");
    }

    this->port = newPort;
    return this->port;
}


int HTTPServer::setNewBacklog(int newBacklog) {
    if (this->backlog == newBacklog) {
        logger->info("[HTTPSERVER] 警告!设定的完成队列(backlog)和原端口一致!");
    }

    this->backlog = newBacklog;
    return this->backlog;
}

// epoll + 线程池 启动HTTPServe
void HTTPServer::run() {
    // 启动监听 获取一个服务器socket的文件描述符
    this->serverSockfd = this->getServerSocketAndListen();

    // 创建epoll文件描述符
    this->epollFd = epoll_create1(0);

    if (this->epollFd == -1) {
        logger->info("[HTTPSERVER] ERROR 创建EPOLL失败!");
        exit(1);
    }

    this->event.events = EPOLLIN;  //监视有数据可读事件
    this->event.data.fd = this->serverSockfd;

    //将文件描述符添加到epoll实例epoll_fd中，注册监听事件
    //加入监听列表，当 epollFd 上有对应事件产生时， epoll_wait 会将 epoll_event 填充到 events_in 数组里
    if (epoll_ctl(this->epollFd, EPOLL_CTL_ADD, this->serverSockfd, &this->event)) {
        logger->error("[HTTPSERVER] serverSockfd添加到epoll失败:{}({}) errno:{}", __FILE__, __LINE__, errno);
        close(this->epollFd);
        exit(1);
    }

    int cliSockFd(-1), fd(-1);
    size_t reqLen(0);
    
    // 持续监听事件
    while (true) {
        //等待事件， epoll_wait 会将事件填充至 events 内
        //返回 获得的事件数量，若超时且没有任何事件返回 0 ，出错返回 -1 。 timeout设置为-1表示无限等待。
        int eventCount = epoll_wait(this->epollFd, this->events, MAX_EVENTS, -1);  

        if (eventCount < 0) {
            // 出现问题 掠过本次操作 执行下一轮
            logger->info("[HTTPSERVER] epoll_wait失败: {}({}) errno:{}", __FILE__, __LINE__, errno);
            continue;
        }

        for (int i(0); i < eventCount; i++) {
            fd = this->events[i].data.fd;

            // 新的连接请求
            if (fd == this->serverSockfd) {
                socklen_t socket_len = sizeof(struct sockaddr_in);
                memset(this->clientAddress, 0, sizeof(*this->clientAddress));

                // 接受连接请求
                cliSockFd = accept(this->serverSockfd, (struct sockaddr*)this->clientAddress, &socket_len);
                if (cliSockFd < 0) {
                    logger->error("[HTTPSERVER] 调用 accept 失败:{}({})", __FILE__, __LINE__);
                    continue;
                }
                event.data.fd = cliSockFd;
                event.events = EPOLLIN | EPOLLET;  //边缘触发
                /*
                    EPOLL边缘触发和水平触发 知识点说明
                    水平触发：只要缓冲区有数据，epoll_wait就会一直被触发，直到缓冲区为空；
                    边沿触发：只有所监听的事件状态改变或者有事件发生时，epoll_wait才会被触发；
                        epoll边沿触发时，假设一个客户端发送100字节的数据，而服务器设定read每次读取20字节，那么一次触发只能读取20个字节，
                        然后内核调用epoll_wait直到下一次事件发生，才会继续从剩下的80字节读取20个字节，
                        由此可见，这种模式其工作效率非常低且无法保证数据的完整性，因此边沿触发不会单独使用。

                        边沿触发通常与非阻塞IO一起使用，其工作模式为：epoll_wait触发一次，在while（1）循环内非阻塞IO读取数据，
                        直到缓冲区数据为空（保证了数据的完整性），内核才会继续调用epoll_wait等待事件发生。
                */
               // 
                if (epoll_ctl(this->epollFd, EPOLL_CTL_ADD, cliSockFd, &event) < 0) {
                    logger->error("[HTTPSERVER] 调用 epoll_ctl 添加 cliSockFd 失败:{}({}) errno:{}", __FILE__, __LINE__, errno);
                    continue;
                }
            } else {
                // 非连接 即存在数据可读
                cliSockFd = fd;
                memset(this->requestBuf, '\0', sizeof(char) * N);

                if ((reqLen = read(cliSockFd, this->requestBuf, N - 1)) < 0) {  // 字符串结尾确保为 '\0'.
                    logger->error("[HTTPSEVER] 客户端读取失败: {}({})", __FILE__, __LINE__);
                    close(cliSockFd);
                    continue;
                }

                if (reqLen == N - 1) {
                    logger->error("[HTTPSERVER] 请求过大(最大长度{}): {}({})", N, __FILE__, __LINE__);
                    close(cliSockFd);
                    continue;
                }

                // 调用llhttp解析http请求
                llhttp_init(&this->httpParser, HTTP_BOTH, this->httpParserSettings);
                this->httpParser.data = this;  //一定要先绑定，否则reinterpret_cast时有问题
                enum llhttp_errno err = llhttp_execute(&this->httpParser, this->requestBuf, reqLen);

                if (err == HPE_OK && this->requestProcessPtr != nullptr) {
                    try {
                        this->requestProcessPtr->setClientSockfd(cliSockFd);
                        if (this->UseMultiThread) {
                            // 请求由主线程解析，然后由线程池进行具体处理。
                            // 因为llhttp已经很快了，如果是传入request_buf到线程里的话，还会涉及到字符串复制等，速度其实并不快了。
                            
                            std::function<void()> func = [&]() {
                                try {
                                    //run();
                                    this->requestProcessPtr->run();
                                } catch (std::exception& e) {
                                    logger->error("请求执行失败: {}({})", __FILE__, __LINE__);
                                }
                                auto client_sockfd = this->requestProcessPtr->getClientSockfd();
                                auto keep_alive =  this->requestProcessPtr->getkeepAliveStatu();
                                
                                // 不是长连接 关闭客户端的文件描述符
                                if(!keep_alive) { 
                                    close(client_sockfd);
                                }
                            };
                            
                            this->pool->addTask(func);
                        } else {
                            this->requestProcessPtr->run();
                            if (!this->requestProcessPtr->getkeepAliveStatu()) {
                                close(cliSockFd);
                            }
                        }
                    } catch (std::exception& e) {
                        logger->error("[HTTPSERVER] 请求执行失败: {}({})", __FILE__, __LINE__);
                        close(cliSockFd);
                    }
                } else {
                    logger->error("[HTTPSERVER] http解析失败: {}({})", __FILE__, __LINE__);
                    close(cliSockFd);
                }
            }
        }
    }
}


// 回调函数 钩子部分 回调函数部分 0 表示成功 其他失败
// 参考：https://blog.csdn.net/theanarkh/article/details/120592787
// 参考：https://blog.csdn.net/zzhongcy/article/details/41981855

int HTTPServer::on_message_begin() {
    this->requestProcessPtr = nullptr;
    this->headFields.clear();
    this->headValues.clear();
    return 0;  
}

int HTTPServer::on_url(llhttp_t *parser, const char *at, size_t length) {
    try {
        this->requestProcessPtr = std::shared_ptr<requestProcessor>(reqProcessorFactory::produceHttpProcessor((llhttp_method) (parser->method)));
        if (this->requestProcessPtr == nullptr) {
            return -1;
        }
        std::string url = std::string(at, length);
        url = this->codec->decoder(url); // 解码url 考虑到url只能通过asc字符集传输
        url = url.substr(1, url.size() - 1);
        this->requestProcessPtr->setUrl(url);
        this->requestProcessPtr->setRedis(this->redis);
        this->requestProcessPtr->setUseRedis(this->UseRedis);
        return 0;
    } catch (std::exception& e) {
        logger->error("[HTTPSERVER] 错误：{}({})", __FILE__, __LINE__);
        return -1;
    }
}

int HTTPServer::on_header_field(const char *at, size_t length) {
    // 解析到 键值对时的操作
    std::string field = std::string(at, length);
    transform(field.begin(), field.end(), field.begin(), ::tolower); //全部转为小写字母
    this->headFields.emplace_back(field);
    return 0;
}

int HTTPServer::on_header_value(const char *at, size_t length) {
    std::string value = std::string(at, length);
    transform(value.begin(), value.end(), value.begin(), ::tolower);//全部转为小写字母
    this->headValues.emplace_back(value);
    return 0;
}

int HTTPServer::on_headers_complete() {
    if (this->headFields.size() != this->headValues.size()) {
        logger->error("[HTTPSERVER] head的field({})和value({})数量不匹配", this->headFields.size(), this->headValues.size());
        return -1;
    }

    // 添加到请求处理类中
    size_t len = this->headFields.size();

    for (size_t i = 0; i < len; i++) {
        this->requestProcessPtr->addHead(this->headFields[i], this->headValues[i]);
    }

    return 0;
}

// 处理请求体
int HTTPServer::on_body(const char *at, size_t len) {
    std::string body = std::string(at, len);
    try {
        this->requestProcessPtr->setMessageBody(body);
    } catch (std::exception& e) {
        logger->info("[HTTPSERVER] 解析http请求在 {} {} 遇到错误", __FILE__, __LINE__);
        return -1;
    }
    return 0;
}

int HTTPServer::on_message_complete() {
    if (this->requestProcessPtr == nullptr) {
        logger->info("[HTTPSERVER] 解析http请求在 {} {} 遇到错误", __FILE__, __LINE__);
        return -1;
    }
    return 0;
}